#pragma once
#include <d3d9.h>
#include <d3dx9.h>
class Vec4;

#define DEGTORAD(degree) ((D3DX_PI / 180.0f) * (degree))
#define RADTODEG(rad) ((180.0f / D3DX_PI) *(rad))

class Vec2 : public D3DXVECTOR2
{
public:

	Vec2(D3DXVECTOR2 &v2) { x = v2.x; y = v2.y; }
	Vec2() : D3DXVECTOR2() {}
	Vec2(const float _x,const float _y) { x = _x; y = _y;}

	inline float Length() const { return D3DXVec2Length(this); }
	inline float Dot(const Vec2 &b) const { return D3DXVec2Dot(this, &b); }
};

class Area
{
public:
	Area(const Vec2& pos, const Vec2& size) { m_position = pos; m_size = size; }
	const Vec2& GetPosition() const { return m_position; }
	const Vec2& GetSize() const { return m_size; }
protected:
	Vec2 m_position;
	Vec2 m_size;
};

class Vec3 : public D3DXVECTOR3
{
public:
	inline float Length() const { return D3DXVec3Length(this); }
	inline void Normalize() { D3DXVec3Normalize(this,this); }
	inline float Dot(const Vec3 &b)const { return D3DXVec3Dot(this, &b); }
	inline bool IsLonger(float dist);
	inline Vec3 Cross(const Vec3 &b)const;
	Vec3(D3DXVECTOR3 &v3) { x = v3.x; y = v3.y; z = v3.z; }
	Vec3(const Vec2 &v2) { x = v2.x; y = v2.y; z = 0; }
	Vec3() : D3DXVECTOR3() {}
	Vec3(const float _x,const float _y, const float _z) { x = _x; y = _y; z = _z; }
	inline Vec3(const class Vec4 &v4);
};

inline bool Vec3::IsLonger(float dist)
{
	float squ = x*x + y*y + z*z;
	if (squ >= dist*dist)
		return true;

	return false;
}

inline Vec3 Vec3::Cross(const Vec3 &b)const
{
	Vec3 out;
	D3DXVec3Cross(&out,this,&b);
	return out;
}

class Vec4 : public D3DXVECTOR4
{
	public:
	inline float Length() const { return D3DXVec4Length(this); }
	inline void Normalize() { D3DXVec4Normalize(this,this); }
	inline float Dot(const Vec4 &b)const { return D3DXVec4Dot(this, &b); }
	inline bool IsLonger(float dist)
	{
		float squ = x*x + y*y + z*z + w*w;
		if (squ >= dist*dist)
			return true;

		return false;
	}
	Vec4(D3DXVECTOR4 &v4) { x = v4.x; y = v4.y; z = v4.z; w = v4.w; }
	Vec4() : D3DXVECTOR4() {}
	Vec4(const float _x,const float _y, const float _z, const float _w) { x = _x; y = _y; z = _z; w = _w; }
	Vec4(const Vec3 &v3) { x = v3.x; y = v3.y; z = v3.z; w = 1.0f;}
};

inline Vec3::Vec3(const Vec4 &v4) { x = v4.x; y = v4.y; z = v4.z; }

class Quaternion : public D3DXQUATERNION
{
public:
	void Normalize() { D3DXQuaternionNormalize(this,this); }
	void Slerp(const Quaternion &begin, const Quaternion &end, float cooef)
	{
		D3DXQuaternionSlerp(this, &begin, &end, cooef);
	}

	void GetAxisAngle(Vec3 &axis, float &angle) const
	{
		D3DXQuaternionToAxisAngle(this, &axis, &angle);
	}

	void BuildRotYawPitchRoll(
				const float yawRadians,
				const float pitchRadians,
				const float rollRadians)
	{
		D3DXQuaternionRotationYawPitchRoll(
			this, yawRadians, pitchRadians, rollRadians);
	}

	void BuildAxisAngle(const Vec3 &axis, const float radians)
	{
		D3DXQuaternionRotationAxis(this, &axis, radians);
	}

	void Build(const class Mat4x4 &mat);
	Quaternion(D3DXQUATERNION &q) : D3DXQUATERNION(q) { }
	Quaternion() : D3DXQUATERNION() { }

	void SetIdentity() { D3DXQuaternionIdentity(this); }
};

struct Steering
{
	Vec3 linear;
	Vec3 angular;

	Steering();
	Steering(Vec3 lin, Vec3 ang)
	{
		linear = lin;
		angular = ang;
	}
};

class Mat4x4: public D3DXMATRIX
{
public:
	inline void SetPosition(Vec3 const &pos)
	{
		m[3][0] = pos.x;
		m[3][1] = pos.y;
		m[3][2] = pos.z;
		m[3][3] = 1.0f;
	}

	inline void SetPosition(Vec4 const &pos)
	{
		m[3][0] = pos.x;
		m[3][1] = pos.y;
		m[3][2] = pos.z;
		m[3][3] = pos.w;
	}

	inline Vec3 GetPosition() const { return Vec3(m[3][0],m[3][1],m[3][2]); }
	inline Vec4 Xform(Vec4 &v)const
	{
		Vec4 temp;
		D3DXVec4Transform(&temp,&v,this);
		return temp;
	}

	inline Vec3 Xform(Vec3 &v)const
	{
		Vec4 temp(v), out;
		D3DXVec4Transform(&out,&temp,this);
		return Vec3(out);
	}

	inline Vec3 XformNormal(Vec3 &v)const
	{
		Vec3 out;
		D3DXVec3TransformNormal(&out, &v, this);
		return out;
	}

	inline Mat4x4 Inverse() const
	{
		Mat4x4 out;
		D3DXMatrixInverse(&out,NULL,this);
		return out;
	}

	inline void BuildTranslation(const Vec3 &pos)
	{
		this->SetIdentity();
		D3DXMatrixTranslation(this, pos.x, pos.y, pos.z);
	}

	inline void BuildRotationX(const float radians) { D3DXMatrixRotationX(this, radians); }
	inline void BuildRotationY(const float radians) { D3DXMatrixRotationY(this, radians); }
	inline void BuildRotationZ(const float radians) { D3DXMatrixRotationZ(this, radians); }
	inline void BuildYawPitchRoll(const float yawRadians, const float pitchRadians, const float rollRadians) 
	{ 
		D3DXMatrixRotationYawPitchRoll(this, yawRadians, pitchRadians, rollRadians); 
	}
	inline void BuildRotationQuat(const Quaternion &q) { D3DXMatrixRotationQuaternion(this, &q); }

	Mat4x4(D3DXMATRIX &mat) { memcpy(&m, &mat.m, sizeof(mat.m)); };
	Mat4x4() : D3DXMATRIX() { }

	void SetIdentity() { D3DXMatrixIdentity(this); }
	static Mat4x4 g_Identity;
};

inline Mat4x4 operator * (const Mat4x4 &a, const Mat4x4 &b)
{
	Mat4x4 out;
	D3DXMatrixMultiply(&out, &a, &b);
	return out;
}

inline Quaternion operator * (const Quaternion &a, const Quaternion &b)
{
	// for rotations, this is exactly like concatenating
	// matrices - the new quat represents rot A followed by rot B.
	Quaternion out;
	D3DXQuaternionMultiply(&out, &a, &b);
	return out;
}

inline void Quaternion::Build(const class Mat4x4 &mat)
{
	D3DXQuaternionRotationMatrix(this, &mat);
}

class Plane : public D3DXPLANE
{
public:
	inline void Normalize();

	// normal faces away from you if you send in verts in counter clockwise order....
	inline void Init(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2);
	bool Inside(const Vec3 &point, const float radius) const;
	bool Inside(const Vec3 &point) const;
};

inline void Plane::Normalize()
{
	float mag;
	mag = sqrt(a * a + b * b + c * c);
	a = a / mag;
	b = b / mag;
	c = c / mag;
	d = d / mag;
}

inline void Plane::Init(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2)
{
	D3DXPlaneFromPoints(this, &p0, &p1, &p2);
	Normalize();
}

////////////////////////////////////////////////////
//
// Frustum Definition
//
////////////////////////////////////////////////////
class Frustum
{
public:
	enum Side { Near, Far, Top, Right, Bottom, Left, NumPlanes };

	Plane m_Planes[NumPlanes];	// planes of the frusum in camera space
	Vec3 m_NearClip[4];			// verts of the near clip plane in camera space
	Vec3 m_FarClip[4];			// verts of the far clip plane in camera space

	float m_Fov;				// field of view in radians
	float m_Aspect;				// aspect ratio - width divided by height
	float m_Near;				// near clipping distance
	float m_Far;				// far clipping distance

public:
	Frustum();

	bool Inside(const Vec3 &point) const;
	bool Inside(const Vec3 &point, const float radius) const;
	const Plane &Get(Side side) { return m_Planes[side]; }
	void SetFOV(float fov) { m_Fov=fov; Init(m_Fov, m_Aspect, m_Near, m_Far); }
	void SetAspect(float aspect) { m_Aspect=aspect; Init(m_Fov, m_Aspect, m_Near, m_Far); }
	void SetNear(float nearClip) { m_Near=nearClip; Init(m_Fov, m_Aspect, m_Near, m_Far); }
	void SetFar(float farClip) { m_Far=farClip; Init(m_Fov, m_Aspect, m_Near, m_Far); }
	void Init(const float fov, const float aspect, const float near, const float far);

	void Render();
};


inline Vec3 CalcVelocity(Vec3 const &pos0, Vec3 const &pos1, float time)
{
	// CalcVelocity - Chapter 15, page 526
	return (pos1 - pos0) / time;
}

inline Vec3 CalcAcceleration(Vec3 const &vel0, Vec3 const &vel1, float time)
{
	// CalcVelocity - Chapter 15, page 526
	return (vel1 - vel0) / time;
}

inline void HandleAccel(Vec3 &pos, Vec3 &vel, Vec3 &accel, float time)
{
	// CalcVelocity - Chapter 15, page 526
	vel += accel * time;
	pos += vel * time;
}

// converts Barycentric coordinates to world coordinates
// inputs are the 3 verts of the triangle, and the u,v barycentric coordinates
extern Vec3 BarycentricToVec3(Vec3 v0, Vec3 v1, Vec3 v2, float u, float v);

extern bool IntersectTriangle( const Vec3& orig, const Vec3& dir,
						Vec3& v0, Vec3& v1, Vec3& v2,
						FLOAT* t, FLOAT* u, FLOAT* v );

